﻿using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using FolderBrowserDialog = System.Windows.Forms.FolderBrowserDialog;

namespace Remote_server
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private Settings settings;
        private RspListener listener;

        /// <summary>
        /// Default constructor, sets up the component and loads settings
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;

            SetVersionInfo();
            txtPort.AllowOnly(s => WindowUtilities.IsDigit(s) && txtPort.Text.Length < "65535".Length);
            txtPassword.AllowOnly(s => !s.Contains(' ') && txtPassword.Text.Length < Constants.MAX_PASSWORD);

            LoadSettings();
        }

        /// <summary>
        /// Called when the window is closing, stores the settings
        /// </summary>
        /// <param name="e">Not used, the action is never cancelled</param>
        protected override void OnClosing(CancelEventArgs e)
        {
            settings["port"] = txtPort.Text;
            settings["password"] = txtPassword.Text;
            settings["shared_folder"] = txtSharedFolder.Text;
            settings["file_handler"] = checkFileHandler.IsChecked.ToString();
            settings["file_handler_write"] = checkFileHandlerWrite.IsChecked.ToString();
            settings["shared"] = checkShared.IsChecked.ToString();
            settings["shared_write"] = checkSharedWrite.IsChecked.ToString();
            settings["play_pause"] = checkPlayPause.IsChecked.ToString();
            settings["volume"] = checkVolume.IsChecked.ToString();
            settings["screenshot"] = checkScreenshot.IsChecked.ToString();
            settings.Save();
            base.OnClosing(e);
        }

        /// <summary>
        /// Reset the text of the log
        /// </summary>
        private void ResetLog()
        {
            log.Text = "";
        }

        /// <summary>
        /// Append text to the log
        /// </summary>
        /// <param name="text">The text to append</param>
        private void Log(string text)
        {
            log.Text += text + "\r\n";
        }

        /// <summary>
        /// Loads the application settings, any unallowed settings
        /// will not be displayed
        /// </summary>
        private void LoadSettings()
        {
            settings = new Settings();
            txtPort.Text = settings["port"];
            if (!WindowUtilities.IsDigit(txtPort.Text) || txtPort.Text.Length > "65535".Length)
                txtPort.Text = "";

            txtPassword.Text = settings["password"];
            txtSharedFolder.Text = settings["shared_folder"];
            checkFileHandler.IsChecked = true.ToString() == settings["file_handler"];
            checkFileHandlerWrite.IsChecked = true.ToString() == settings["file_handler_write"];
            checkShared.IsChecked = true.ToString() == settings["shared"];
            checkSharedWrite.IsChecked = true.ToString() == settings["shared_write"];
            checkPlayPause.IsChecked = true.ToString() == settings["play_pause"];
            checkVolume.IsChecked = true.ToString() == settings["volume"];
            checkScreenshot.IsChecked = true.ToString() == settings["screenshot"];
        }

        /// <summary>
        /// Called when an RspClient has received a command, displays the
        /// text in the log and forwards the event to the CommandHandler
        /// </summary>
        /// <param name="sender">The RspClient sender</param>
        /// <param name="e">EventArgs describing the event</param>
        private void CommandReceived(object sender, CommandReceivedEventArgs e)
        {
            Dispatcher.BeginInvoke(new Action(() =>
            {
                Log(String.Format("Command received from [{0}]: {1}",
                    (sender as RspClient).RemoteAddress, e.Command));
            }));
            CommandHandler.HandleCommand(e);
        }

        /// <summary>
        /// Called when an RspClient has disconnected,
        /// this method will update the log accordingly
        /// </summary>
        /// <param name="sender">The RspClient that was closed</param>
        private void ClientDisconnected(RspClient sender)
        {
            Dispatcher.BeginInvoke(new Action(() =>
            {
                Log(String.Format("Client disconnected: [{0}]", (sender as RspClient).RemoteAddress));
            }));
        }

        /// <summary>
        /// Displays information about a connected RspClient in the
        /// log, wires up listeners and starts the event dispatcher
        /// of the client
        /// </summary>
        /// <param name="client">The RspClient that was logged on</param>
        private void ClientAccepted(RspClient client)
        {
            Dispatcher.BeginInvoke(new Action(() =>
            {
                Log("Client accepted: " + client.RemoteAddress);
            }));
            client.CommandReceived += CommandReceived;
            client.Closed += ClientDisconnected;
            client.StartEventDispatcher();
        }

        /// <summary>
        /// Start the listening process by displaying
        /// the log, resetting it and wiring up the event listeners
        /// to the RspListener
        /// </summary>
        /// <param name="listener">The RspListener that should
        /// be used</param>
        private void StartService(RspListener listener)
        {
            try
            {
                listener.Start();

                foreach (TabItem ti in tabs.Items)
                    ti.IsEnabled = false;

                logTab.IsEnabled = true;
                logTab.Visibility = Visibility.Visible;
                tabs.SelectedItem = logTab;
                ResetLog();
                Log("Waiting for connections...");

                listener.ClientAccepted += (o, e) => ClientAccepted(e.Client);
                this.listener = listener;
                listener.AcceptRspClients();
            }
            catch
            {
                MessageBox.Show("There was an error starting the service. Please try another port number.");
            }
        }

        /// <summary>
        /// Stop listening
        /// </summary>
        private void StopService()
        {
            listener.Stop();
            foreach (TabItem ti in tabs.Items)
                ti.IsEnabled = true;
            logTab.Visibility = Visibility.Collapsed;

            tabs.SelectedIndex = 0;
        }

        /// <summary>
        /// Loads the version info and displays it in the
        /// user interface
        /// </summary>
        private void SetVersionInfo()
        {
            Assembly asm = Assembly.GetCallingAssembly();
            AssemblyDescriptionAttribute desc = (AssemblyDescriptionAttribute)
                AssemblyDescriptionAttribute.GetCustomAttribute(asm, typeof(AssemblyDescriptionAttribute));

            versionInfo.Text = "Version " + asm.GetName().Version + ", " + desc.Description;
        }

        /// <summary>
        /// Checks whether the entered password is sufficiently short
        /// or not to long and displays an error message if it is not,
        /// if the password is valid any displayed message will be reset
        /// </summary>
        private void UpdatePasswordErrorMessage()
        {
            string pass = txtPassword.Text;
            if (pass.Length == 0)
                errPassword.Content = "Missing password";
            else if (pass.Length < Constants.MIN_PASSWORD)
                errPassword.Content = String.Format("{0} {1} {2}",
                    "Password should be atleast", Constants.MIN_PASSWORD, "characters long");
            else
                errPassword.Content = "";
        }

        /// <summary>
        /// Checks whether the entered port number is a valid digit
        /// ranging from Constants.MIN_PORT to Constants.MAX_PORT
        /// and displays an error message if it is not, if the port
        /// is valid any displayed error message will be reset
        /// </summary>
        private void UpdatePortErrorMessage()
        {
            int port;
            if (int.TryParse(txtPort.Text, out port))
            {
                if (port < Constants.MIN_PORT)
                    errPort.Content = String.Format("{0} {1}",
                        "Port should be atleast", Constants.MIN_PORT);
                else if (port > Constants.MAX_PORT)
                    errPort.Content = String.Format("{0} {1}",
                        "Port should be at most", Constants.MAX_PORT);
                else
                    errPort.Content = "";
            }
            else
                errPort.Content = "Missing port";    
        }

        /// <summary>
        /// Check if there are any errors prior to starting
        /// the listening service
        /// </summary>
        /// <param name="rsp">The RspListener that will be
        /// used when starting the service</param>
        /// <returns>True if there were no errors, false otherwise</returns>
        private bool CheckErrors(RspListener rsp)
        {
            if (rsp.Commands.Count == 0)
                MessageBox.Show("No services are activated.");
            else if (checkShared.IsChecked == true &&
                !Directory.Exists(txtSharedFolder.Text))
                MessageBox.Show("The shared folder does not exist.");
            else
                return true;

            return false;
        }

        /// <summary>
        /// Called when the Start button is clicked, this method
        /// creates an RspListener, and if there are no errors
        /// the service will be started
        /// </summary>
        private void StartClick(object sender, RoutedEventArgs e)
        {
            if (HasValidPort && HasPassword)
            {
                RspListener rsp
                    = new RspListener(ushort.Parse(txtPort.Text), txtPassword.Text);
                if (checkFileHandler.IsChecked == true || checkShared.IsChecked == true)
                    rsp.AddCommand(Commands.FileManager);
                if (checkFileHandlerWrite.IsChecked == true && checkFileHandler.IsChecked == true ||
                    checkSharedWrite.IsChecked == true && checkShared.IsChecked == true)
                    rsp.AddCommand(Commands.Write);
                if (checkPlayPause.IsChecked == true)
                    rsp.AddCommand(Commands.Media);
                if (checkVolume.IsChecked == true)
                    rsp.AddCommand(Commands.Volume);
                if (checkScreenshot.IsChecked == true)
                    rsp.AddCommand(Commands.ScreenShot);

                if (CheckErrors(rsp))
                    StartService(rsp);
            }
        }

        /// <summary>
        /// Called when the Stop button is clicked, this
        /// method stops the listening service
        /// </summary>
        private void CloseClick(object sender, RoutedEventArgs e)
        {
            StopService();
        }

        /// <summary>
        /// Called when text has changed in any of the port or password
        /// textboxes, this method updates the error messages
        /// </summary>
        private void TextChanged(object sender, TextChangedEventArgs e)
        {
            startButton.IsEnabled = IsReadyToStart;
            if (sender == txtPassword)
                UpdatePasswordErrorMessage();
            else
                UpdatePortErrorMessage();
        }

        /// <summary>
        /// Called when the user wants to select a folder to share,
        /// this method displays the WinAPI folder browser dialog to
        /// fetch the path
        /// </summary>
        private void SelectSharedClick(object sender, RoutedEventArgs e)
        {
            FolderBrowserDialog dlg = new FolderBrowserDialog();
            dlg.ShowNewFolderButton = true;
            dlg.Description = "Select a folder to share";
            if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                txtSharedFolder.Text = dlg.SelectedPath;
        }

        /// <summary>
        /// Check whether we are ready to start the listening
        /// service
        /// </summary>
        private bool IsReadyToStart
        {
            get
            {
                return HasValidPort && HasPassword;
            }
        }

        /// <summary>
        /// Check if any password has been entered
        /// </summary>
        private bool HasPassword
        {
            get
            {
                return txtPassword.Text.Length >= Constants.MIN_PASSWORD;
            }
        }

        /// <summary>
        /// Check if a port number has been entered
        /// </summary>
        private bool HasValidPort
        {
            get
            {
                ushort u;
                if (ushort.TryParse(txtPort.Text, out u))
                    return u > Constants.MIN_PORT;
                return false;
            }
        }
    }
}
